// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package aes

import (
	"crypto/cipher"
	subtleoverlap "crypto/internal/subtle"
	"crypto/subtle"
	"encoding/binary"
	"errors"
	"internal/cpu"
)

// 该文件包含AES-GCM的两个实现。第一次实施
// （gcmAsm）使用KMCTR指令在计数器模式下使用AES进行加密，并且
// GHASH的KIMD指令。第二个实现（gcmKMA）使用
// 执行这两种操作的较新的KMA指令。

// gcmCount表示一个16字节的big-endian计数值。
type gcmCount [16]byte

// inc将计数值最右边的32位递增1。
func (x *gcmCount) inc() {
	binary.BigEndian.PutUint32(x[len(x)-4:], binary.BigEndian.Uint32(x[len(x)-4:])+1)
}

// GCMLENGHTS将len0 | | len1作为大端值写入16字节数组。
func gcmLengths(len0, len1 uint64) [16]byte {
	v := [16]byte{}
	binary.BigEndian.PutUint64(v[0:], len0)
	binary.BigEndian.PutUint64(v[8:], len1)
	return v
}

// gcmHashKey表示GHASH算法所需的16字节哈希键。
type gcmHashKey [16]byte

type gcmAsm struct {
	block     *aesCipherAsm
	hashKey   gcmHashKey
	nonceSize int
	tagSize   int
}

const (
	gcmBlockSize         = 16
	gcmTagSize           = 16
	gcmMinimumTagSize    = 12 // NIST SP 800-38D建议使用12个或更多字节的标签。
	gcmStandardNonceSize = 12
)

var errOpen = errors.New("cipher: message authentication failed")

// 断言aesCipherAsm实现了gcmAble接口。
var _ gcmAble = (*aesCipherAsm)(nil)

// NewGCM返回以Galois计数器模式包装的AES密码。这只是
// 由crypto/cipher.NewGCM通过gcmAble接口调用。
func (c *aesCipherAsm) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) {
	var hk gcmHashKey
	c.Encrypt(hk[:], hk[:])
	g := gcmAsm{
		block:     c,
		hashKey:   hk,
		nonceSize: nonceSize,
		tagSize:   tagSize,
	}
	if cpu.S390X.HasAESGCM {
		g := gcmKMA{g}
		return &g, nil
	}
	return &g, nil
}

func (g *gcmAsm) NonceSize() int {
	return g.nonceSize
}

func (g *gcmAsm) Overhead() int {
	return g.tagSize
}

// sliceForAppend获取一个切片和请求的字节数。它返回一个
// 切片，其中给定切片的内容后跟如此多的字节和
// 第二个片，别名为它，只包含额外的字节。如果
// 原始片具有足够的容量，则不执行分配。
func sliceForAppend(in []byte, n int) (head, tail []byte) {
	if total := len(in) + n; cap(in) >= total {
		head = in[:total]
	} else {
		head = make([]byte, total)
		copy(head, in)
	}
	tail = head[len(in):]
	return
}

// ghash使用ghash算法对具有给定密钥的数据进行散列。首字母
// 哈希值由哈希值给出，哈希值将用新的哈希值更新。
// 数据长度必须是16字节的倍数。
// 围棋：noescape
func ghash(key *gcmHashKey, hash *[16]byte, data []byte)

// paddedGHASH用零填充数据，直到其长度为的倍数
// 16字节。然后使用GHASH算法计算哈希的新值。
func (g *gcmAsm) paddedGHASH(hash *[16]byte, data []byte) {
	siz := len(data) &^ 0xf // 将大小对齐到16字节
	if siz > 0 {
		ghash(&g.hashKey, hash, data[:siz])
		data = data[siz:]
	}
	if len(data) > 0 {
		var s [16]byte
		copy(s[:], data)
		ghash(&g.hashKey, hash, s[:])
	}
}

// cryptBlocksGCM在计数器模式下使用给定的
// 功能代码和键。计数器最右边的32位递增
// GCM规范要求的每个块之间。初始计数器值
// 由cnt给出，并用下一个计数器值的值更新
// 使用。
// None
// dst和buf的长度必须大于或等于长度
// src的。在执行过程中，buf可能被部分或完全覆盖
// 函数的类型。
// 围棋：noescape
func cryptBlocksGCM(fn code, key, dst, src, buf []byte, cnt *gcmCount)

// counterCrypt在计数器模式下使用AES加密src并放置结果
// 进入dst。cnt是初始计数值，将使用下一个
// 计数值。dst的长度必须大于或等于该长度
// src的。
func (g *gcmAsm) counterCrypt(dst, src []byte, cnt *gcmCount) {
	// 将src复制到缓冲区可以提高某些模型的性能
	// src和dst指向同一个底层数组。我们还需要一个
	// 计数器值的缓冲区。
	var ctrbuf, srcbuf [2048]byte
	for len(src) >= 16 {
		siz := len(src)
		if len(src) > len(ctrbuf) {
			siz = len(ctrbuf)
		}
		siz &^= 0xf // 将大小对齐到16字节
		copy(srcbuf[:], src[:siz])
		cryptBlocksGCM(g.block.function, g.block.key, dst[:siz], srcbuf[:siz], ctrbuf[:], cnt)
		src = src[siz:]
		dst = dst[siz:]
	}
	if len(src) > 0 {
		var x [16]byte
		g.block.Encrypt(x[:], cnt[:])
		for i := range src {
			dst[i] = src[i] ^ x[i]
		}
		cnt.inc()
	}
}

// deriveCounter根据给定的nonce计算初始GCM计数器状态。
// 见NIST SP 800-38D，第7.1节。
func (g *gcmAsm) deriveCounter(nonce []byte) gcmCount {
	// 对于初始计数器，GCM有两种操作模式
	// 状态：96位（12字节）nonce的“快速路径”和“慢速路径”
	// 对于其他长度的nonce。对于96位的nonce，nonce沿
	// 使用从1开始的四字节big-endian计数器
	// 直接作为起始计数器。对于其他临时尺寸，计数器
	// 通过将其传递给GHASH函数来计算。
	var counter gcmCount
	if len(nonce) == gcmStandardNonceSize {
		copy(counter[:], nonce)
		counter[gcmBlockSize-1] = 1
	} else {
		var hash [16]byte
		g.paddedGHASH(&hash, nonce)
		lens := gcmLengths(0, uint64(len(nonce))*8)
		g.paddedGHASH(&hash, lens[:])
		copy(counter[:], hash[:])
	}
	return counter
}

// auth计算GHASH（密文，附加数据），用
// 标记掩码并将结果写入输出。
func (g *gcmAsm) auth(out, ciphertext, additionalData []byte, tagMask *[gcmTagSize]byte) {
	var hash [16]byte
	g.paddedGHASH(&hash, additionalData)
	g.paddedGHASH(&hash, ciphertext)
	lens := gcmLengths(uint64(len(additionalData))*8, uint64(len(ciphertext))*8)
	g.paddedGHASH(&hash, lens[:])

	copy(out, hash[:])
	for i := range out {
		out[i] ^= tagMask[i]
	}
}

// Seal对明文进行加密和认证。有关详细信息，请参阅cipher.AEAD接口
// 细节。
func (g *gcmAsm) Seal(dst, nonce, plaintext, data []byte) []byte {
	if len(nonce) != g.nonceSize {
		panic("crypto/cipher: incorrect nonce length given to GCM")
	}
	if uint64(len(plaintext)) > ((1<<32)-2)*BlockSize {
		panic("crypto/cipher: message too large for GCM")
	}

	ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize)
	if subtleoverlap.InexactOverlap(out[:len(plaintext)], plaintext) {
		panic("crypto/cipher: invalid buffer overlap")
	}

	counter := g.deriveCounter(nonce)

	var tagMask [gcmBlockSize]byte
	g.block.Encrypt(tagMask[:], counter[:])
	counter.inc()

	var tagOut [gcmTagSize]byte
	g.counterCrypt(out, plaintext, &counter)
	g.auth(tagOut[:], out[:len(plaintext)], data, &tagMask)
	copy(out[len(plaintext):], tagOut[:])

	return ret
}

// Open对密文进行身份验证和解密。请参阅cipher.AEAD接口
// 详情请参阅。
func (g *gcmAsm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
	if len(nonce) != g.nonceSize {
		panic("crypto/cipher: incorrect nonce length given to GCM")
	}
	// 健全性检查，以防止在实现失败时验证始终成功
	// 例如，未初始化标记大小。
	if g.tagSize < gcmMinimumTagSize {
		panic("crypto/cipher: incorrect GCM tag size")
	}
	if len(ciphertext) < g.tagSize {
		return nil, errOpen
	}
	if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(BlockSize)+uint64(g.tagSize) {
		return nil, errOpen
	}

	tag := ciphertext[len(ciphertext)-g.tagSize:]
	ciphertext = ciphertext[:len(ciphertext)-g.tagSize]

	counter := g.deriveCounter(nonce)

	var tagMask [gcmBlockSize]byte
	g.block.Encrypt(tagMask[:], counter[:])
	counter.inc()

	var expectedTag [gcmTagSize]byte
	g.auth(expectedTag[:], ciphertext, data, &tagMask)

	ret, out := sliceForAppend(dst, len(ciphertext))
	if subtleoverlap.InexactOverlap(out, ciphertext) {
		panic("crypto/cipher: invalid buffer overlap")
	}

	if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
		// AESNI代码同时解密和认证，以及
		// 因此，在标记不匹配的情况下覆盖dst。那个
		// 在这里模仿行为是为了在整个过程中保持一致
		// 平台。
		for i := range out {
			out[i] = 0
		}
		return nil, errOpen
	}

	g.counterCrypt(out, ciphertext, &counter)
	return ret, nil
}

// gcmKMA使用KMA指令实现cipher.AEAD接口。它应该
// 仅在hasKMA为真时使用。
type gcmKMA struct {
	gcmAsm
}

// KMA指令的标志
const (
	kmaHS      = 1 << 10 // 提供了哈希子键
	kmaLAAD    = 1 << 9  // 附加认证数据的最后一系列
	kmaLPC     = 1 << 8  // 最后一系列明文或密文块
	kmaDecrypt = 1 << 7  // 解密
)

// kmaGCM执行fn给出的加密或解密操作。标签
// 将计算并写入标记。cnt应包含当前的
// 计数器状态，并将被更新的计数器状态覆盖。
// TODO（mundaym）：无法传入哈希子键
// 围棋：noescape
func kmaGCM(fn code, key, dst, src, aad []byte, tag *[16]byte, cnt *gcmCount)

// Seal对明文进行加密和认证。有关详细信息，请参阅cipher.AEAD接口
// 细节。
func (g *gcmKMA) Seal(dst, nonce, plaintext, data []byte) []byte {
	if len(nonce) != g.nonceSize {
		panic("crypto/cipher: incorrect nonce length given to GCM")
	}
	if uint64(len(plaintext)) > ((1<<32)-2)*BlockSize {
		panic("crypto/cipher: message too large for GCM")
	}

	ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize)
	if subtleoverlap.InexactOverlap(out[:len(plaintext)], plaintext) {
		panic("crypto/cipher: invalid buffer overlap")
	}

	counter := g.deriveCounter(nonce)
	fc := g.block.function | kmaLAAD | kmaLPC

	var tag [gcmTagSize]byte
	kmaGCM(fc, g.block.key, out[:len(plaintext)], plaintext, data, &tag, &counter)
	copy(out[len(plaintext):], tag[:])

	return ret
}

// Open对密文进行身份验证和解密。请参阅cipher.AEAD接口
// 详情请参阅。
func (g *gcmKMA) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
	if len(nonce) != g.nonceSize {
		panic("crypto/cipher: incorrect nonce length given to GCM")
	}
	if len(ciphertext) < g.tagSize {
		return nil, errOpen
	}
	if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(BlockSize)+uint64(g.tagSize) {
		return nil, errOpen
	}

	tag := ciphertext[len(ciphertext)-g.tagSize:]
	ciphertext = ciphertext[:len(ciphertext)-g.tagSize]
	ret, out := sliceForAppend(dst, len(ciphertext))
	if subtleoverlap.InexactOverlap(out, ciphertext) {
		panic("crypto/cipher: invalid buffer overlap")
	}

	if g.tagSize < gcmMinimumTagSize {
		panic("crypto/cipher: incorrect GCM tag size")
	}

	counter := g.deriveCounter(nonce)
	fc := g.block.function | kmaLAAD | kmaLPC | kmaDecrypt

	var expectedTag [gcmTagSize]byte
	kmaGCM(fc, g.block.key, out[:len(ciphertext)], ciphertext, data, &expectedTag, &counter)

	if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
		// AESNI代码同时解密和认证，以及
		// 因此，在标记不匹配的情况下覆盖dst。那个
		// 在这里模仿行为是为了在整个过程中保持一致
		// 平台。
		for i := range out {
			out[i] = 0
		}
		return nil, errOpen
	}

	return ret, nil
}
